package simplecert

import (
	"errors"
	"fmt"
	"io"
	"time"

	"gitee.com/shuohe/go-shlib"
)

//=============================================================================

type CaInfo struct {
	Name       string
	KeySuite   string
	PrivateKey shlib.IPrivateKey
	PublicKey  shlib.IPublicKey
}

// 仅用于验证证书
var DefaultVerifyCa = &CaInfo{
	Name:       "SimpleCert CA",
	KeySuite:   "2019.01",
	PrivateKey: nil,
	PublicKey:  shlib.LoadKeyFromBase58Ne("3N7H1WCwg26qzuLP3yyS7f3RVWZHhStMjt6kk6AABanRHQaTFtVbdQq4N949").(shlib.IPublicKey),
}

//=============================================================================

/**
 * 简单数字证书对象。
 *
 * @implements:
 *		xbuf.Marshalable
 */
type SimpleCert struct {
	// 证书格式版本号
	Version string

	// 证书序列号
	SerialNumber int64

	// 证书所代表的实体名字
	IdentityName string

	// 证书用途
	Usage string

	// 证书公钥
	PublicKey shlib.IPublicKey

	// 签发时间
	SignDate time.Time

	// 证书过期日
	ExpireDate time.Time

	// 对证书进行签名的 CA 的名字
	CaName string

	// CA 对证书签名所用的密钥
	CaKeySuite string

	// CA 对证书的签名
	CaSignature []byte
}

func (this *SimpleCert) marshalContentToXBuf(xb *shlib.XBuffer) {
	xb.PutVStr(this.Version)
	xb.PutVInt64(this.SerialNumber)
	xb.PutVStr(this.IdentityName)
	xb.PutVStr(this.Usage)
	this.PublicKey.ExportToXBuf(xb)

	b, _ := this.SignDate.MarshalBinary()
	xb.PutVBytes(b)

	b, _ = this.ExpireDate.MarshalBinary()
	xb.PutVBytes(b)

	xb.PutVStr(this.CaName)
	xb.PutVStr(this.CaKeySuite)
}

/**
 * 对证书的内容签名，并填写签名字段内容。
 */
func (this *SimpleCert) Sign(caInfo *CaInfo) {
	this.CaName = caInfo.Name
	this.CaKeySuite = caInfo.KeySuite

	xb := shlib.NewXBuffer(4096)
	this.marshalContentToXBuf(xb)

	this.CaSignature = caInfo.PrivateKey.Sign(xb.Bytes())
}

/**
 * 校验证书签名的正确性。
 */
func (this *SimpleCert) Verify(caInfo *CaInfo) bool {
	if this.CaName != caInfo.Name || this.CaKeySuite != caInfo.KeySuite {
		return false
	}

	var buf [4096]byte
	xb := shlib.XBuffer{buf[:], 0, 0, true}

	this.marshalContentToXBuf(&xb)

	return caInfo.PublicKey.Verify(xb.Bytes(), this.CaSignature)
}

/**
 * @implements xbuf.Marshalable::MarshalToXBuf
 */
func (this *SimpleCert) MarshalToXBuf(xb *shlib.XBuffer) {
	this.marshalContentToXBuf(xb)
	xb.PutVBytes(this.CaSignature)
}

/**
 * @implements xbuf.Unmarshalable::UnmarshalFromXBuf
 */
func (this *SimpleCert) UnmarshalFromXBuf(xb *shlib.XBuffer) error {
	var err error
	var pk shlib.IKey
	var b []byte

	if this.Version, err = xb.GetVStr(); err != nil {
		return errors.New("Can not unmarshal Version.")
	}

	if this.SerialNumber, err = xb.GetVInt64(); err != nil {
		return errors.New("Can not unmarshal SerialNumber.")
	}

	if this.IdentityName, err = xb.GetVStr(); err != nil {
		return errors.New("Can not unmarshal IdentityName.")
	}

	if this.Usage, err = xb.GetVStr(); err != nil {
		return errors.New("Can not unmarshal Usage.")
	}

	if pk, err = shlib.LoadKeyFromXBuf(xb); err != nil {
		return errors.New("Can not unmarshal PublicKey.")
	}
	this.PublicKey = pk.(shlib.IPublicKey)

	if b, err = xb.GetVBytes(); err != nil {
		return errors.New("Can not unmarshal SignDate.")
	}
	if err = this.SignDate.UnmarshalBinary(b); err != nil {
		return errors.New("Can not unmarshal SignDate.")
	}

	if b, err = xb.GetVBytes(); err != nil {
		return errors.New("Can not unmarshal ExpireDate.")
	}
	if err = this.ExpireDate.UnmarshalBinary(b); err != nil {
		return errors.New("Can not unmarshal ExpireDate.")
	}

	if this.CaName, err = xb.GetVStr(); err != nil {
		return errors.New("Can not unmarshal CaName.")
	}

	if this.CaKeySuite, err = xb.GetVStr(); err != nil {
		return errors.New("Can not unmarshal CaKeySuite.")
	}

	if this.CaSignature, err = xb.GetVBytes(); err != nil {
		return errors.New("Can not unmarshal CaSignature.")
	}

	return nil
}

func (this *SimpleCert) SaveToFile(fileName string) error {
	return shlib.SaveToFile(this, fileName)
}

func (this *SimpleCert) LoadFromFile(fileName string) error {
	return shlib.LoadFromFile(this, fileName)
}

func (this *SimpleCert) Dump(w io.Writer) {
	fmt.Fprintln(w, "Version       :", this.Version)
	fmt.Fprintln(w, "Serial Number :", this.SerialNumber)
	fmt.Fprintln(w, "Identity Name :", this.IdentityName)
	fmt.Fprintln(w, "Usage         :", this.Usage)
	fmt.Fprintln(w, "Public Key    :", shlib.Base58Encode(this.PublicKey.Export()))
	fmt.Fprintln(w, "Sign Date     :", this.SignDate.String())
	fmt.Fprintln(w, "Expire Date   :", this.ExpireDate.String())
	fmt.Fprintln(w, "CA Name       :", this.CaName)
	fmt.Fprintln(w, "CA Key Suite  :", this.CaKeySuite)
	fmt.Fprintln(w, "CA Signature  :", shlib.Base58Encode(this.CaSignature))
}

func LoadSimpleCert(filename string) (*SimpleCert, error) {
	cert := &SimpleCert{}
	if err := cert.LoadFromFile(filename); err != nil {
		return nil, err
	}

	return cert, nil
}

func LoadSimpleCertNe(filename string) *SimpleCert {
	cert := &SimpleCert{}
	if err := cert.LoadFromFile(filename); err != nil {
		panic(err)
	}

	return cert
}
